/*
	Copyright (C) 2011 Salil Bhagurkar

	This file is part of illusion

	illusion is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as
	published by the Free Software Foundation, either version 3 of
	the License, or (at your option) any later version.

	illusion is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with illusion. 
	If not, see <http://www.gnu.org/licenses/>.
*/

#include <klib/lib.h>
#include <klib/memory.h>
#include <klib/format.h>
#include <kernel/list.h>
#include <kernel/console.h>
#include <kernel/vfs.h>
#include <kernel/kmalloc.h>
#include <kernel/sys.h>
#include <kernel/errors.h>
#include <kernel/build.h>

struct sys_interface {
	struct vfs_node *node;				//The VFS node on which this interface listens
	void *(*read)(uint_t *len);		//Reads everything
	int (*write)(void *buf, uint_t len);	//Writes info
	struct sys_interface *next, *prev;
};

static struct sys_interface *interfaces = null, *interfaces_tail = null;

void register_sys_interface(struct vfs_node *node, void *(*read)(uint_t *len),
		int (*write)(void *buf, uint_t len))
{
	struct sys_interface *n = (struct sys_interface *)kmalloc(sizeof(struct sys_interface), "sysi");
	n->node = node;
	n->read = read;
	n->write = write;
	n->next = n->prev = null;
	list_attach(interfaces, interfaces_tail, n);
}

static struct sys_interface *find_interface(struct vfs_node *node)
{
	struct sys_interface *intf;
	list_for(interfaces, intf) {
		if(intf->node == node)
			return intf;
	}
	return null;
}

void unregister_sys_interface(struct vfs_node *node)
{
	struct sys_interface *i = find_interface(node);
	if(!i)
		assertv(false, "Unregistered sys interface: %s\n", node->name);
	list_detach(interfaces, interfaces_tail, i);
	kfree(i, sizeof(struct sys_interface), "sysi");
}

static struct vfs_node *sys_get_child(struct vfs_node *node) {return null;}
static struct vfs_node *sys_next_child(struct vfs_node *child) {return null;}


static int sys_sync_page(struct vfs_node *node, struct vfs_page *page)
{
	struct sys_interface *intf = find_interface(node);
	if(!intf)
		return ENOENT;
	if(page->flags & VFS_SYNC_DEV) {
		console_printf("sys: VFS_SYNC_DEV\n");
		memcpy(page->page_data, node->class + page->start, page->length);
		return 0;
	} else if(page->flags & VFS_SYNC_MEM) {
		console_printf("sys: VFS_SYNC_MEM\n");
		uint_t new_length = page->start + page->length;
		if(new_length > node->length) {
			void *new_mem = kmalloc(new_length, "sys");
			memcpy(new_mem, node->class, node->length);
			node->class = new_mem;
			node->length = new_length;
		}
		memcpy(node->class + page->start, page->page_data, page->length);
		struct sys_interface *intf = find_interface(node);
		intf->write(node->class, node->length);
		return 0;
	}
	return ENOSUP;
}

static int sys_sync_node(struct vfs_node *node)
{
	struct sys_interface *intf = find_interface(node);
	if(!intf)
		return ESTINV;
	if(node->flags & VFS_SYNC_DEV) {
		//Free previous data
		if(node->class != null) {
			kfree(node->class, node->length, "sys");
		}
		uint_t len;
		void *data = intf->read(&len);
		node->class = data;
		node->length = len;
		return 0;
	}
	return ENOSUP;
}


static struct vfs_sync_provider sys_prov = {
		.flags = VFS_SP_NOPAGECACHE | VFS_SP_NONODECACHE,
		.name = "sys_prov",
		.supported_page_streams = "data",
		.sync_node = sys_sync_node,
		.sync_page = sys_sync_page,
		.get_child = sys_get_child,
		.next_child = sys_next_child,
};

struct vfs_node *sys_root;

static char *read_const(uint_t c, uint_t *len)
{
	char buf[10];
	*len = format_string(buf, 10, "%u", c);
	char *ret_buf = (char *)kmalloc(*len, "bdsys");
	memcpy(ret_buf, buf, *len);
	return ret_buf;
}

static void *read_bd(uint_t *len)
{
	return read_const(BUILD_DATE, len);
}

static void *read_bn(uint_t *len)
{
	return read_const(BUILD_NUMBER, len);
}

static void sys_stuff_init()
{
	struct vfs_node *bd = vfs_create(sys_root, "build_date");
	struct vfs_node *bn = vfs_create(sys_root, "build_number");
	register_sys_interface(bd, read_bd, null);
	register_sys_interface(bn, read_bn, null);
}

int sys_vfs_init()
{
	initmsg("Creating sys interfaces..\n");
	sys_root = vfs_create_device("sys", 1)->root;
	vfs_set_sync_provider(sys_root->dev, &sys_prov);
	sys_stuff_init();
	return 0;
}
